//
//  SJDownloader.m
//  dancebaby
//
//  Created by BlueDancer on 2018/4/28.
//  Copyright © 2018年 hunter. All rights reserved.
//

#import "SJDownloader.h"
#import <objc/message.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSURLSessionTask (SJDownloaderAdd)
@property (nonatomic, strong, nullable) SJDownloader *downloader;
@property (nonatomic, copy, nullable) void(^downloadProgressBlock)(SJDownloader *downloader, float progress);
@property (nonatomic, copy, nullable) void(^successBlock)(SJDownloader *downloader, NSURL *tmpPath);
@property (nonatomic, copy, nullable) void(^failure)(SJDownloader *downloader);
@end

@implementation NSURLSessionTask (SJDownloaderAdd)
- (SJDownloader * __nullable)downloader {
    return objc_getAssociatedObject(self, _cmd);
}
- (void)setDownloader:(SJDownloader *__nullable)downloader {
    objc_setAssociatedObject(self, @selector(downloader), downloader, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void (^__nullable)(SJDownloader * _Nonnull, float))downloadProgressBlock {
    return objc_getAssociatedObject(self, _cmd);
}
- (void)setDownloadProgressBlock:(void (^__nullable)(SJDownloader * _Nonnull, float))downloadProgressBlock {
    objc_setAssociatedObject(self, @selector(downloadProgressBlock), downloadProgressBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (void (^__nullable)(SJDownloader * _Nonnull, NSURL * _Nonnull))successBlock {
    return objc_getAssociatedObject(self, _cmd);
}
- (void)setSuccessBlock:(void (^__nullable)(SJDownloader * _Nonnull, NSURL * _Nonnull))successBlock {
    objc_setAssociatedObject(self, @selector(successBlock), successBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (void (^__nullable)(SJDownloader * _Nonnull))failure {
    return objc_getAssociatedObject(self, _cmd);
}
- (void)setFailure:(void (^__nullable)(SJDownloader * _Nonnull))failure {
    objc_setAssociatedObject(self, @selector(failure), failure, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end

@interface SJDownloader ()<NSURLSessionDelegate>
@property (nonatomic, strong, readonly) NSURLSession *downloadSession; // shared
@property (nonatomic, strong, readonly) NSOperationQueue *taskQueue; // shared
@property (nonatomic, weak, nullable) NSURLSessionDownloadTask *current;
@end

@implementation SJDownloader
- (instancetype)init {
    self = [super init];
    if ( !self ) return nil;
    static NSURLSession *downloadSession;
    static NSOperationQueue *taskQueue;
    if ( !taskQueue ) {
        taskQueue = [NSOperationQueue new];
        taskQueue.maxConcurrentOperationCount = 1;
        taskQueue.name = @"com.sjmediadownloader.taskqueue";
    }
    if ( !downloadSession ) {
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
        downloadSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:taskQueue];
    }
    _taskQueue = taskQueue;
    _downloadSession = downloadSession;
    return self;
}
- (void)dealloc {
    NSLog(@"%d - %s - %@", (int)__LINE__, __func__, self);
    if ( _current && _current.state != NSURLSessionTaskStateCompleted ) [_current cancel];
}
- (void)downloadFileWithURLStr:(NSString *)URLStr
              downloadProgress:(void(^__nullable)(SJDownloader *downloader, float progress))downloadProgressBlock
                       success:(void(^__nullable)(SJDownloader *downloader, NSURL *tmpPath))successBlock
                       failure:(void(^__nullable)(SJDownloader *downloader))failureBlock {
    NSLog(@"准备缓存: %@ - %@", URLStr, self);
    NSData *resumeData = [NSData dataWithContentsOfFile:resumePath(URLStr)];
    NSURLSessionDownloadTask *task;
    if ( resumeData ) {
        task = [self.downloadSession downloadTaskWithResumeData:resumeData];
    }
    else {
        task = [self.downloadSession downloadTaskWithURL:[NSURL URLWithString:URLStr]];
    }
    _current = task;
    [_current resume];
    _current.downloader = self;
    _current.downloadProgressBlock = downloadProgressBlock;
    _current.successBlock = successBlock;
    _current.failure = failureBlock;
}

static NSString *resumePath(NSString *URLStr) {
    return [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%ld", (long)[URLStr hash]]];
}

- (void)cancelCompeletion:(void(^__nullable)(SJDownloader *downloader))completion {
    NSString *URLStr = _current.currentRequest.URL.absoluteString;
    __weak typeof(self) _self = self;
    [_current cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
        __strong typeof(_self) self = _self;
        if ( !self ) return;
        if ( !resumeData ) return ;
        NSString *path = resumePath(URLStr);
        NSString *folder = [path stringByDeletingLastPathComponent];
        if ( ![[NSFileManager defaultManager] fileExistsAtPath:folder] ) {
            [[NSFileManager defaultManager] createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:nil error:nil];
        }
        [resumeData writeToFile:path atomically:YES];
        if ( completion ) completion(self);
    }];
}

#pragma mark - URLSession Delegate Methods
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    if ( totalBytesExpectedToWrite == 0 ) return;
    downloadTask.downloadProgressBlock(downloadTask.downloader, 1.0 * totalBytesWritten / totalBytesExpectedToWrite);
}
- (void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(nonnull NSURL *)location {
    downloadTask.successBlock(downloadTask.downloader, location);
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    if ( !error ) return;
    if ( error.code == NSURLErrorCancelled ) return;
    _error = error;
    task.failure(task.downloader);
}
@end
NS_ASSUME_NONNULL_END
